#include "Coro.h"
#include <setjmp.h>

// stack size 2M
#define assert(x) if ((x) == 0) {for( ;; );}

#define CORO_FUN_INIT 0
#define CORO_FUN_END 1
#define CORO_FUN_YIELD 2
#define CORO_FUN_START 3
#define CORO_FUN_SEMPOST 4

extern unsigned int Coro_GetTickMs();
extern void Coro_start( jmp_buf buf);

struct Coro
{
	struct Coro *prev, *next;

	int co_id;
	CoroFunType co_fun;
	void* co_arg;
	void* co_stack;
	int co_stack_size;
	unsigned int resume_time;
	struct Sem_t* wait_sem;
	jmp_buf co_env;
};
static struct CoroPrivate_t
{
	unsigned char StackBuf[MAXSTACKBUFSIZE];
	struct Coro CoroBuf[MAXCOROCNT];
	struct Coro* current_coro;
	struct Coro* coro_list;
	int coro_num;
	int coro_stack_used;
	jmp_buf main_env;
}CoroPrivate=
{
	.coro_num=0,
	.coro_stack_used=128,
}; 
void co_idle(char* msg)
{
	while (1)
	{
		coro_yield();
	}
}
static void _start_coro()
{

	CoroPrivate.current_coro->co_fun(CoroPrivate.current_coro->co_arg);
	longjmp(CoroPrivate.main_env, CORO_FUN_END);
}
void coro_new(CoroFunType f, void* arg,int stackSize)
{
	if(CoroPrivate.coro_num>MAXCOROCNT)
	{
		assert(0)
	}
	struct Coro* co = &CoroPrivate.CoroBuf[CoroPrivate.coro_num];
	assert(co);

	if (CoroPrivate.coro_list)
	{
		co->prev = CoroPrivate.coro_list->prev;
		co->next = CoroPrivate.coro_list;
		CoroPrivate.coro_list->prev->next = co;
		CoroPrivate.coro_list->prev = co;
	}
	else
	{
		co->prev = co;
		co->next = co;
	}
	CoroPrivate.coro_list = co;

	// init struct Coro
	co->co_id = CoroPrivate.coro_num++;
	co->co_fun = f;
	co->co_arg = arg;
	co->co_stack = &CoroPrivate.StackBuf[CoroPrivate.coro_stack_used];
	co->co_stack_size = (int)(stackSize/8)*8+16;
	CoroPrivate.coro_stack_used += co->co_stack_size;
	assert(CoroPrivate.coro_stack_used<MAXSTACKBUFSIZE);

	// init jmp buf
	setjmp(co->co_env);
//	_JUMP_BUFFER* jmp_buf = (_JUMP_BUFFER*)&(co->co_env);
//
	co->co_env[9] = (unsigned long)(_start_coro);
	co->co_env[8] = (unsigned long)((char*)(co->co_stack) + co->co_stack_size - 16);
}
int GetTimeDiff(unsigned int t0,unsigned int t1)
{
	int timeDiff = t0-t1;
	return timeDiff;
}
void coro_main()
{
	if (!CoroPrivate.coro_list)
		return;
	coro_new(co_idle, "idle",128);
	struct Coro *co;
	unsigned int cutTick;
	int timeDiff;

	switch (setjmp(CoroPrivate.main_env))
	{
	case CORO_FUN_INIT:
		// scheduling
		CoroPrivate.main_env[8] = &CoroPrivate.StackBuf[128]-16;
		CoroPrivate.current_coro = CoroPrivate.coro_list;
		Coro_start(CoroPrivate.current_coro->co_env);
		break;

	case CORO_FUN_END:
		co = CoroPrivate.current_coro;
		if (co->next == co)
		{
			CoroPrivate.coro_list = 0;
			return;
		}
		// scheduling
		CoroPrivate.current_coro = CoroPrivate.current_coro->next;
		co->prev->next = co->next;
		co->next->prev = co->prev;
		break;

	case CORO_FUN_YIELD:
		// scheduling
		cutTick = Coro_GetTickMs();
		do
		{
			CoroPrivate.current_coro = CoroPrivate.current_coro->next;
			timeDiff = GetTimeDiff(cutTick,CoroPrivate.current_coro->resume_time);
		} while ((timeDiff<0)||(0 != CoroPrivate.current_coro->wait_sem));
		CoroPrivate.current_coro->resume_time=cutTick;
		break;
	case CORO_FUN_SEMPOST:
		do
		{
			CoroPrivate.current_coro = CoroPrivate.current_coro->next;
			if((0 !=CoroPrivate.current_coro->wait_sem)&&
				(CoroPrivate.current_coro->wait_sem->cnt>0))
			{
				break;
			}
		} while (1);
		break;
	case CORO_FUN_START:
		break;
	}
	assert(CoroPrivate.current_coro);
	longjmp(CoroPrivate.current_coro->co_env, 1);
}

void coro_yield()
{
	if (setjmp(CoroPrivate.current_coro->co_env))
		return;
	longjmp(CoroPrivate.main_env, CORO_FUN_YIELD);
}

void coro_sleepms(int time_ms)
{
	if (setjmp(CoroPrivate.current_coro->co_env))
		return;
	CoroPrivate.current_coro->resume_time =  Coro_GetTickMs()+time_ms;
	longjmp(CoroPrivate.main_env, CORO_FUN_YIELD);
}
void coro_wait(struct Sem_t* sem)
{
	if(sem->cnt<1)
	{
		if (0==setjmp(CoroPrivate.current_coro->co_env))
		{
			CoroPrivate.current_coro->wait_sem = sem;
			longjmp(CoroPrivate.main_env, CORO_FUN_YIELD);
		}

	}
	sem->cnt--;
}


void coro_post(struct Sem_t* sem)
{
	sem->cnt++;
	if (setjmp(CoroPrivate.current_coro->co_env))
	{
		return;
	}
	longjmp(CoroPrivate.main_env, CORO_FUN_SEMPOST);
}
